// 
//  Copyright 2011  Joel Johnson
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace TemplateCompiler {
	public class XmlObject {
		public String Namespace {get;private set;}
		public String Name {get; private set;}
		public IEnumerable<XmlObject> Children { get { foreach(var c in children) yield return c; } }
		public IEnumerable<String> AttributeNames {
			get {
				foreach(String key in attributes.Keys) {
					yield return key;
				}
			}
		}
		
		private List<XmlObject> children = new List<XmlObject>();
		private Dictionary<String, String> attributes = new Dictionary<String, String>();
		private bool canAddChildren = true;
		public bool PrettyPrint { get; set;}
	
		public XmlObject(String name) : this(null, name) {}
		public XmlObject(String namespaceName, String name) {
			this.Namespace = namespaceName;
			this.Name = name;
			PrettyPrint = true;
		}
			
		public XmlObject(String content, bool isContent) {
			children.Add(new XmlContent(content));
			canAddChildren = false;
		}
		
		public static XmlObject Parse(String xml) {
			int removeChars = 2;
			if(xml.EndsWith("/>")) {
				removeChars++;
			}
			String[] split = xml.Substring(1, xml.Length - removeChars).Split(new []{' '}, StringSplitOptions.RemoveEmptyEntries);
			String name = split[0];
			XmlObject rtn;
			if(name.Contains(":")) {
				String[] namespaceSplit = name.Split(new []{':'}, 2, StringSplitOptions.RemoveEmptyEntries);
				String namespaceValue = namespaceSplit[0];
				String nameValue = namespaceSplit[1];
				rtn = new XmlObject(namespaceValue, nameValue);
			} else {
				rtn = new XmlObject(name);
			}
			
			for(int i = 1; i < split.Length; i++) {
				String attribute = split[i];
				String[] attributeSplit = attribute.Split(new []{'='}, 2, StringSplitOptions.RemoveEmptyEntries);
				if(attributeSplit.Length == 2) {
					rtn.AddAttribute(attributeSplit[0], attributeSplit[1]);
				} else if(attributeSplit.Length == 1) {
					rtn.AddAttribute(attributeSplit[0], "");
				} else {
					throw new XmlParseException();
				}
			}
			
			return rtn;
		}
		
		public String GetAttribute(String name) {
			return attributes[name];
		}
	
		public XmlObject AddAttribute(String name, String value) {
			if((value.StartsWith("\"") && value.EndsWith("\"")) || 
			   		(value.StartsWith("'") && value.EndsWith("'"))) {
				value = value.Substring(1, value.Length-2);
			}
			attributes[name] = value;
			return this;
		}
	
		public XmlObject AddTag(XmlObject tag) {
			if(!canAddChildren) {
				throw new Exception("You cannot add a child to a tag with text content");
			}
			children.Add(tag);
			return this;
		}
	
		public XmlObject AddTags(params XmlObject[] tags) {
			foreach(XmlObject tag in tags) {
				AddTag(tag);
			}
			return this;
		}
	
		protected virtual String ToString(int spaces, bool prettyPrint) {
			String s = "";
			String nl = "\n";
			if(prettyPrint) {
				for(int i = 0; i < spaces; i++) {
					s += "  ";
				}
			} else {
				nl = "";
			}
	
			StringBuilder sb = new StringBuilder();
			sb.Append(s).Append("<");
			if(Namespace != null) {
				sb.Append(Namespace).Append(":");
			}
			sb.Append(Name);
			if(attributes.Count > 0) {
				foreach(String key in attributes.Keys) {
					String value = attributes[key];
					String quote = value.Contains("\"") ? "'" : "\"";
					sb.Append(" ").Append(key).Append("=").Append(quote).Append(value).Append(quote);
				}
			}
			if(children.Count <= 0) {
				sb.Append("/>");
			} else {
				sb.Append(">").Append(nl);
				foreach(XmlObject tag in children) {
					sb.Append(tag.ToString(spaces + 1, prettyPrint)).Append(nl);
				}
				sb.Append(s).Append("</");
				if(Namespace != null) {
					sb.Append(Namespace).Append(":");
				}
				sb.Append(Name);
				sb.Append(">");
			}
			return sb.ToString();
		}
	
		public override String ToString() {
			return this.ToString(0, PrettyPrint);
		}
		
		public String OpenTag() {
			StringBuilder sb = new StringBuilder();
			sb.Append("<");
			if(Namespace != null) {
				sb.Append(Namespace).Append(":");
			}
			sb.Append(Name);
			if(attributes.Count > 0) {
				foreach(String key in attributes.Keys) {
					String value = attributes[key];
					String quote = value.Contains("\"") ? "'" : "\"";
					sb.Append(" ").Append(key).Append("=").Append(quote).Append(value).Append(quote);
				}
			}
			if(children.Count <= 0) {
				sb.Append("/>");
			} else {
				sb.Append(">");
			}
			return sb.ToString();
		}
		
		public String CloseTag() {
			if(children.Count <= 0) {
				return "";
			}
			
			StringBuilder sb = new StringBuilder();
			sb.Append("</");
			if(Namespace != null) {
				sb.Append(Namespace).Append(":");
			}
			sb.Append(Name);
			sb.Append(">");
			return sb.ToString();
		}
	}
	
	public class XmlContent : XmlObject {
		public String Content { get; private set; }

		public XmlContent(String content) : base("") {
			this.Content = content;
		}

		public override String ToString() {
			return ToString(0, PrettyPrint);
		}

		protected override String ToString(int spaces, bool prettyPrint) {
			String s = "";
			if(prettyPrint) {
				for(int i = 0; i < spaces; i++) {
					s += "  ";
				}
			}
			return s + Content;
		}
	}
	
	public class XmlSpecial : XmlObject {
		public String Content { get; private set; }
		public String SpecialCharacter { get; private set;}

		public XmlSpecial(String content) : base("") {
			this.Content = content;
			this.SpecialCharacter = "";
		}
		
		public XmlSpecial(String content, String specialCharacter) : base("") {
			this.Content = content;
			this.SpecialCharacter = specialCharacter;
		}
		
		public new static XmlSpecial Parse(String tag) {
			XmlSpecial rtn;
			if(tag.Length >= 4 && tag.StartsWith("<%") && tag.EndsWith("%>")) {
				tag = tag.Substring(2, tag.Length - 4);
			}
			
			if(tag.StartsWith("=") || tag.StartsWith("@")) {
				String specialCharacter = tag[0].ToString();
				rtn = new XmlSpecial(tag.Substring(1), specialCharacter);
			} else {
				rtn = new XmlSpecial(tag);
			}
			
			return rtn;
		}

		public override String ToString() {
			return ToString(0, PrettyPrint);
		}

		protected override String ToString(int spaces, bool prettyPrint) {
			String s = "";
			if(prettyPrint) {
				for(int i = 0; i < spaces; i++) {
					s += "  ";
				}
			}
			return String.Format("{0}<%{1}{2}%>", s, SpecialCharacter, Content);
		}
	}
}

